#include <stdio.h>

int main(int argc, char **argv)
{
    // 在 FILE 结构内部记录了这样一些信息：文件内部的当前读写位置、读写报错的记录、文件结尾指示器、缓冲区开始位置的指针、文件标识符、一个计数器（统计拷贝进缓冲区的字节数）等等。后继的操作就可以使用这个指针（而不是文件名）来处理指定文件。
    // 同时，它还为文件建立一个缓存区。由于存在缓存区，也可以说fopen()函数“打开一个了流”，后继的读写文件都是流模式。
    FILE *fp;
    // fgetc()一调用，文件的数据块先拷贝到缓冲区。不同的计算机有不同的缓冲区大小，一般是512字节或是它的倍数，如4096或16384。随着计算机硬盘容量越来越大，缓冲区也越来越大。
    // fgetc()从缓冲区读取数据，同时将文件指针内部的读写位置指示器，指向所读取字符的下一个字符。所有的文件读取函数都使用相同的缓冲区，后面再调用任何一个读取函数，都将从指示器指向的位置，即上一次读取函数停止的位置开始读取。
    // 当读取函数发现已读完缓冲区里面的所有字符时，会请求把下一个缓冲区大小的数据块，从文件拷贝到缓冲区中。读取函数就以这种方式，读完文件的所有内容，直到文件结尾。不过，上例是只从缓存区读取一个字符。当函数在缓冲区里面，读完文件的最后一个字符时，就把 FILE 结构里面的文件结尾指示器设置为真。于是，下一次再调用读取函数时，会返回常量 EOF。EOF 是一个整数值，代表文件结尾，一般是-1。
    // 原型: FILE* fopen(char* filename, char* mode);
    fp = fopen("data.txt", "r");
    // fopen()的模式字符串有以下几种。
    //
    // r：读模式，只用来读取数据。如果文件不存在，返回 NULL 指针。
    // w：写模式，只用来写入数据。如果文件存在，文件长度会被截为0，然后再写入；如果文件不存在，则创建该文件。
    // a：写模式，只用来在文件尾部追加数据。如果文件不存在，则创建该文件。
    // r+：读写模式。如果文件存在，指针指向文件开始处，可以在文件头部添加数据。如果文件不存在，返回 NULL 指针。
    // w+：读写模式。如果文件存在，文件长度会被截为0，然后再写入数据。这种模式实际上读不到数据，反而会擦掉数据。如果文件不存在，则创建该文件。
    // a+：读写模式。如果文件存在，指针指向文件结尾，可以在现有文件末尾添加内容。如果文件不存在，则创建该文件。

    // 上一小节说过，fopen()函数会为打开的文件创建一个缓冲区。读模式下，创建的是读缓存区；写模式下，创建的是写缓存区；读写模式下，会同时创建两个缓冲区。C 语言通过缓存区，以流的形式，向文件读写数据。
    // 数据在文件里面，都是以二进制形式存储。但是，读取的时候，有不同的解读方法：以原本的二进制形式解读，叫做“二进制流”；将二进制数据转成文本，以文本形式解读，叫做“文本流”。写入操作也是如此，分成以二进制写入和以文本写入，后者会多一个文本转二进制的步骤。
    // fopen()的模式字符串，默认是以文本流读写。如果添加b后缀（表示 binary），就会以“二进制流”进行读写。比如，rb是读取二进制数据模式，wb是写入二进制数据模式。
    // 模式字符串还有一个x后缀，表示独占模式（exclusive）。如果文件已经存在，则打开文件失败；如果文件不存在，则新建文件，打开后不再允许其他程序或线程访问当前文件。比如，wx表示以独占模式写入文件，如果文件已经存在，就会打开失败。

    if (fp == NULL)
    {
        puts("read file fail");
        return -1;
    }
    int c;
    c = fgetc(fp);
    // 循环读取整个文件内容
    while (c != EOF)
    {
        printf("%c", c);
        c = fgetc(fp);
    }
    // fclose()关闭文件，同时清空缓存区
    // 原型：int fclose(FILE* stream);
    // 它接受一个文件指针fp作为参数。如果成功关闭文件，fclose()函数返回整数0；如果操作失败（比如磁盘已满，或者出现 I/O 错误），则返回一个特殊值 EOF（详见下一小节）。
    // 不再使用的文件，都应该使用fclose()关闭，否则无法释放资源。一般来说，系统对同时打开的文件数量有限制，及时关闭文件可以避免超过这个限制。
    int closed = fclose(fp);
    if (closed != 0)
    {
        printf("close file error");
    }
    /*
    EOF
    C 语言的文件操作函数的设计是，如果遇到文件结尾，就返回一个特殊值。程序接收到这个特殊值，就知道已经到达文件结尾了。
    头文件stdio.h为这个特殊值定义了一个宏EOF（end of file 的缩写），它的值一般是-1。这是因为从文件读取的二进制值，不管作为无符号数字解释，还是作为 ASCII 码解释，都不可能是负值，所以可以很安全地返回-1，不会跟文件本身的数据相冲突。
    需要注意的是，不像字符串结尾真的存储了\0这个值，EOF并不存储在文件结尾，文件中并不存在这个值，完全是文件操作函数发现到达了文件结尾，而返回这个值。
    */
    return 0;
}